/*
 * Copyright (c) 2009, Hamish Morgan. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the University of Sussex nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package locusts.client.renderer;

import java.awt.BasicStroke;
import java.awt.geom.NoninvertibleTransformException;
import java.util.logging.Level;
import java.util.logging.Logger;
import locusts.client.*;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JPanel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import locusts.common.ObservableList;
import locusts.common.Player;
import locusts.common.World;
import locusts.lib.FrameCounter;

/**
 *
 * @author Hamish Morgan
 */
public class RenderPanel extends JPanel {

    private static final ResourceBundle strings = ResourceBundle.getBundle(
            "strings", Locale.getDefault());
    RenderConfig rConfig;
    World world;
//    public ViewTransformKeyListener keyList;
    Timer timer;
    FrameCounter frameCounter;
    double lastTick = 0;
    BackgroundRenderer bgRenderer;
    OverlayRenderer ovRenderer;
    EntityListRenderer enRenderer;
    private final ClientConfig config;
    private final ObservableList<Player> players;

    public RenderPanel(ClientConfig config, ObservableList<Player> players) {
        this.config = config;
        this.players = players;
        setSize(config.getWidth(), config.getHeight());
        setVisible(true);
//        setIgnoreRepaint(true);
        world = new World(1, 1);
        rConfig = new RenderConfig();
//        setFocusable(true);
//        keyList = new ViewTransformKeyListener();
//        addKeyListener(keyList);
//        requestFocus();
        bgRenderer = new BackgroundRenderer(world);
        ovRenderer = new OverlayRenderer();
        enRenderer = new EntityListRenderer(world);
//        createBufferStrategy(1);
        config.addChangeListener(new ChangeHandler());
    }

    private class ChangeHandler implements ChangeListener {

        public void stateChanged(ChangeEvent e) {
            if (e.getSource() instanceof ClientConfig) {
                final ClientConfig cfg = (ClientConfig) e.getSource();
                setSize(cfg.getWidth(), cfg.getHeight());
//                keyList.
            }
        }
    }

    public OverlayRenderer getOvRenderer() {
        return ovRenderer;
    }

    public BackgroundRenderer getBgRenderer() {
        return bgRenderer;
    }

    public EntityListRenderer getEnRenderer() {
        return enRenderer;
    }

    public void start() {
        frameCounter = new FrameCounter("Client:Graphics");
        timer = new Timer("Server Game Thread");
        lastTick = System.currentTimeMillis();
        timer.schedule(new UpdateTimer(), 0, 1000L / 100L);
    }

    public void stop() {
        timer.cancel();
        timer.purge();
    }

    private class UpdateTimer extends TimerTask {

        @Override
        public void run() {
            repaint();
        }
    }

    public void setWorld(World world) {
        this.world = world;
    }

    public World getWorld() {
        return world;
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        if (g.getClipBounds().isEmpty())
            return;
        if (!isShowing())
            return;
        render(g);
    }

    @Override
    public void setBounds(int x, int y, int width, int height) {
        super.setBounds(x, y, width, height);
//        if (keyList != null)
//            keyList.setBounds(x, y, width, height);
        if (config.getWidth() != getWidth() || config.getHeight() !=
                getHeight()) {
            config.setSize(getWidth(), getHeight());
        }
    }

    private void render(Graphics g) {
        final double period = frameCounter.getTimeSinceLastFrameSeconds();
        frameCounter.markFrame();
        if (world != null) {
            world.update(period);
        }
        Graphics2D g2 = (Graphics2D) g;
//        rConfig.setAntialiasing(true);
//        rConfig.setTextAntialiasing(true);
//        rConfig.setFractionalMetrics(false);
//        rConfig.setInterpolation(RenderConfig.INTERPOLATION_NEAREST_NEIGHBOR);
//        rConfig.setQualityAlphaIterpolation(false);
//        rConfig.setQualityRendering(false);
//        rConfig.setDithering(true);
//        rConfig.setPureStrokeControl(false);
//        rConfig.setQualityColorRendering(false);

        g2.setRenderingHints(rConfig.getRenderingHints());

        AffineTransform saveAT = g2.getTransform();

//        g2.translate(-world.getWidth() / 2, -world.getHeight() / 2);
//        g2.translate(getWidth() / 2, getHeight() / 2);
//        if (world != null)
//            g2.translate(-world.getWidth() / 2, -world.getHeight() / 2);

//        keyList.update(period);
        g2.transform(config.getTransform());
//        g2.transform(keyList.getTransform());

        bgRenderer.render(g2);
        enRenderer.render(g2);
//
        g2.setTransform(saveAT);

        ovRenderer.render(g2);
    }

    public class OverlayRenderer {

        private List<ClientConfig> clientConfigs;
        private boolean drawClientViews;
        private boolean drawFpsCounter;
        private boolean drawInstructions;
        private final Color text_background_color = new Color(255, 255, 127,
                127);
        private final Color text_color = Color.BLACK;
        private final int TEXT_BORDER = 4;
        private final Stroke VIEW_AREA_STROKE = new BasicStroke(4,
                BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 100,
                new float[]{10, 10}, 0);
        private final Color VIEW_AREA_COLOR = Color.red;

        public OverlayRenderer() {
            drawClientViews = false;
            drawFpsCounter = false;
            drawInstructions = false;
        }

        public void render(Graphics2D g) {

            drawClientViews(g);

            drawFpsCounter(g);

            drawInstructions(g);

            drawGameOver(g);

        }

        private void drawGameOver(Graphics2D g) {
            if (world.getState() != World.State.GAME_OVER)
                return;

            // grey the background
            g.setColor(new Color(127, 127, 127, 127));
            g.fillRect(0, 0, getWidth(), getHeight());

            int hpad = 10;
            int vpad = 10;
            // Draw some text
            final String title = "Game Over";

            final String[] headings = new String[]{"Player", "Kills",
                "Damage", "Time Survived", "Score"};

            final String[][] data =
                    new String[players.size()][headings.length];


            {
                int i = 0;
                for (Player player : players) {
                    data[i][0] = player.getName();
                    data[i][1] = "" + player.getKills();
                    data[i][2] = "" + (int) player.getDamage();
                    data[i][3] = "" + (int) player.getTime();
                    data[i][4] = "" + (int) (player.getScore());
                    i++;
                }
            }

            Font titleFont = g.getFont().deriveFont(Font.BOLD, 40);
            Font headingFont = g.getFont().deriveFont(Font.BOLD, 20);
            Font dataFont = g.getFont().deriveFont(Font.PLAIN, 20);
            FontMetrics titleMetrics = g.getFontMetrics(titleFont);
            FontMetrics headingMetrics = g.getFontMetrics(headingFont);
            FontMetrics dataMetrics = g.getFontMetrics(dataFont);

            int width = 0;
            int height = 0;

            height += titleMetrics.getHeight();
            height += vpad + headingMetrics.getHeight();
            height += (vpad + dataMetrics.getHeight()) * players.size();

            width = Math.max(width, titleMetrics.stringWidth(title));

            int[] colWidths = new int[data[0].length];
            Arrays.fill(colWidths, 0);
            for (int i = 0; i < colWidths.length; i++) {
                colWidths[i] = Math.max(
                        colWidths[i],
                        headingMetrics.stringWidth(headings[i]));
            }

            for (int i = 0; i < data.length; i++) {
                for (int j = 0; j < data[i].length; j++) {
                    colWidths[j] = Math.max(
                            colWidths[j],
                            dataMetrics.stringWidth(data[i][j]));
                }
            }
            int sum = 0;
            for (int i = 0; i < colWidths.length; i++)
                sum += colWidths[i];
            if (colWidths.length > 0)
                sum += hpad * (colWidths.length - 1);

            width = Math.max(width, sum);

            final int left = getWidth() / 2 - width / 2;
            final int top = getHeight() / 2 - height / 2;

            int x = left;
            int y = top;

            g.setColor(Color.black);

            g.setFont(titleFont);
            x = getWidth() / 2 - titleMetrics.stringWidth(title) / 2;
            y += titleMetrics.getHeight();
            g.drawString(title, x, y);

            g.setFont(headingFont);
            x = left;
            y += vpad + headingMetrics.getHeight();
            for (int i = 0; i < colWidths.length; i++) {
                g.drawString(headings[i], x, y);
                x += colWidths[i] + hpad;
            }

            g.setFont(dataFont);
            for (int i = 0; i < data.length; i++) {
                x = left;
                y += vpad + dataMetrics.getHeight();
                for (int j = 0; j < data[i].length; j++) {
                    g.drawString(data[i][j], x, y);
                    x += hpad + colWidths[j];
                }
            }


        }

        private void drawFpsCounter(Graphics2D g) {
            if (!drawFpsCounter)
                return;

            String fps = frameCounter.toString();
            Rectangle2D rec = g.getFontMetrics().getStringBounds(fps, g);
            g.setColor(text_background_color);
            g.fillRect(0, 0, (int) rec.getWidth() + 2 * TEXT_BORDER,
                    (int) rec.getHeight() + 2 * TEXT_BORDER);
            g.setColor(text_color);
            g.drawString(frameCounter.toString(), TEXT_BORDER,
                    (int) rec.getHeight() + TEXT_BORDER);

        }

        private void drawInstructions(Graphics2D g) {
            if (!drawInstructions)
                return;

            g.setColor(text_background_color);
            g.fillRect(0, getHeight() - 25, getWidth(), 25);
            g.setColor(text_color);
            g.drawString(strings.getString("view_instructions"), 10,
                    getHeight() - 10);

        }

        private void drawClientViews(Graphics2D g) {
            if (!isDrawClientViews() || clientConfigs == null ||
                    clientConfigs.isEmpty()) {
                return;
            }

            final Stroke saveStroke = g.getStroke();
            final Color saveColor = g.getColor();
            g.setStroke(VIEW_AREA_STROKE);
            g.setColor(VIEW_AREA_COLOR);

            final FontMetrics fm = g.getFontMetrics();

            final AffineTransform saveAt = g.getTransform();
            for (ClientConfig cfg : clientConfigs) {
                try {
                    if (cfg != config) {
                        g.transform(config.getTransform());
                        g.transform(cfg.getTransform().createInverse());
                    }
                    g.drawRect(0, 0, cfg.getWidth(), cfg.getHeight());
                    g.drawString(cfg.toString(), TEXT_BORDER, TEXT_BORDER +
                            fm.getHeight());

                } catch (NoninvertibleTransformException ex) {
                    Logger.getLogger(RenderPanel.class.getName()).
                            log(Level.SEVERE, null, ex);
                } finally {
                    g.setTransform(saveAt);
                }

            }

            g.setStroke(saveStroke);
            g.setColor(saveColor);
        }

        public void setClientConfigs(List<ClientConfig> clientConfigs) {
            this.clientConfigs = clientConfigs;
        }

        public List<ClientConfig> getClientConfigs() {
            return clientConfigs;
        }

        public void setDrawClientViews(boolean drawClientViews) {
            this.drawClientViews = drawClientViews;
        }

        public boolean isDrawClientViews() {
            return drawClientViews;
        }
    }
}
